/*
	game.cc		Game States
	Copyright (c) 2004, 2005, 2006, 2010 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "config.h"

#include "game.h"
#include "opening.h"
#include "proginfo.h"
#include "iter.h"
#include "randboard.h"
#include "rand.h"
#include "alphabeta.h"
#include "pattern.h"
#include "book.h"
#include "hash.h"
#include "fdstream.h"
#include "boardio.h"

#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h> 
#include <unistd.h>
#include <fcntl.h>
#include <windows.h>
#include <winbase.h>

#ifdef _
#  undef _
#endif

#ifdef N_
#  undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) (x)

/*************************************************************************
	AI variables
*************************************************************************/

pthread_t	ai_thread;
pthread_attr_t	ai_thread_attr;
bool		ai_running;
bool		ai_tampered;

pthread_mutex_t	input_lock = PTHREAD_MUTEX_INITIALIZER;
volatile int	input_pos;			// Lock required for access

/*************************************************************************
	Game states
*************************************************************************/

void new_game_game_mode();

void new_game(game_info &g)
{
	new_game_game_mode();

	if (start_game_mode == START_GAME_RANDOM) {
		byte_board_info b;
		random_board(&b, start_random_game_pieces);
		move_queue.clear();
		g.new_game_from_board(&b, BLACK);
	}
	else {
		move_queue = opening_move_queue;
		g.new_game_from_begin(move_queue.size());
	}
}

void new_game(game_info &g, const byte_board_info &b, int color)
{
	new_game_game_mode();

	move_queue.clear();
	g.new_game_from_board(&b, color);
}


game_info cur_game_info;

std::deque<int>	move_queue;

int		time_player;
int		clock_player;
int		time_player_animate_delay;

/*************************************************************************
	Configurations - Not front-end specific
*************************************************************************/

				// Must be COMPUTER_NONE for proper
				// working of the first call to
				// set_game_mode during program start up
game_mode_type game_mode = COMPUTER_NONE;
bool	black_ai = false;
bool	white_ai = false;

const char *game_mode_name[NUM_GAME_MODE] = {
	N_("None"), N_("Black"), N_("White"), 
	N_("Both"), N_("Alternate"), N_("Random")
};

level_info_type level_info[NUM_LEVEL_INFO] = {
	{ 2, 6, 4 },
	{ 4, 8, 6 },
	{ 6, 12, 10 },
	{ 8, 16, 14 },
	{ 10, 20, 18 }
};

int computer_level = 2;

level_info_type current_level_info;

const char *level_name[NUM_LEVEL_INFO+1] = {
	N_("1"), N_("2"), N_("3"), N_("4"), N_("5"), N_("Custom")
};

int opening_var;
const char *opening_var_name[NUM_OPENING_VAR] = {
	N_("None"), N_("Very low"), N_("Low"), N_("Medium"), N_("High"), N_("Very high")
};

bool hint_move;
bool show_last_move;
bool log_move;
bool animate_opening;
bool redo_ai_move;

int animate_delay;

std::string opening_name;
int opening_player;
std::deque<int> opening_move_queue;

const char *start_game_mode_name[NUM_START_GAME_MODE] = {
	N_("Initial board"), N_("Random board")
};
start_game_mode_type start_game_mode;
int start_random_game_pieces;

bool show_border;
bool show_toolbar;
bool show_pattern_evaluation;
bool show_game_history;
std::string theme_name;

byte_board_info *view_board_ptr;
view_mode_type view_mode;
int view_position;
int view_player;

/*************************************************************************
	Game mode functions
*************************************************************************/

game_mode_type get_game_mode()
{
	return game_mode;
}

void set_game_mode(game_mode_type game_mode_)
{
	game_mode_type old_mode = game_mode;
	game_mode = game_mode_;

	switch (game_mode) {
		case COMPUTER_NONE:
			black_ai = false;
			white_ai = false;
			break;
		case COMPUTER_BLACK:
			black_ai = true;
			white_ai = false;
			break;
		case COMPUTER_WHITE:
			black_ai = false;
			white_ai = true;
			break;
		case COMPUTER_BOTH:
			black_ai = true;
			white_ai = true;
			break;
		case COMPUTER_ALTERNATE:
		case COMPUTER_RANDOM:
					// This avoid changing AI
					// color while game is in
					// progress
			if (old_mode != COMPUTER_BLACK
			    && old_mode != COMPUTER_WHITE) {
				if (get_random(2) & 1) {
					black_ai = true;
					white_ai = false;
				}
				else {
					black_ai = false;
					white_ai = true;
				}
			}
			break;
	}
}

const char *get_game_mode_string()
{
	return _(game_mode_name[static_cast<int>(game_mode)]);
}

bool is_computer_player(int color)
{
	if (color == BLACK)
		return black_ai;
	else
		return white_ai;
}

void switch_computer_game_mode()
{
	switch (get_game_mode()) {
		case COMPUTER_BLACK:
			set_game_mode(COMPUTER_WHITE);
			break;
		case COMPUTER_WHITE:
			set_game_mode(COMPUTER_BLACK);
			break;
		case COMPUTER_ALTERNATE:
		case COMPUTER_RANDOM:
			{
				bool temp_ai = black_ai;
				black_ai = white_ai;
				white_ai = temp_ai;
			}
			break;
		default:
				// COMPUTER_NONE or COMPUTER_BOTH
			break;
	}
}

void new_game_game_mode()
{
	if (game_mode == COMPUTER_ALTERNATE
	    || (game_mode == COMPUTER_RANDOM && (get_random(2) & 1))) {
		bool temp_ai = black_ai;
		black_ai = white_ai;
		white_ai = temp_ai;
	}
}

bool get_wait_player()
{
	return cur_game_info.is_game_play()
		&& !is_computer_player(cur_game_info.get_player())
		&& !move_queue.size();
}

void maybe_set_ai_tampered()
{
				// No move played yet
	if (cur_game_info.num_history <= cur_game_info.min_num_history)
		return;
				// Any first move is equivalent and
				// doesn't affect the game
	if (cur_game_info.num_history <= 2
	    && !cur_game_info.is_random_game())

	ai_tampered = true;
}

int	get_computer_move(double komi)
{
	int	pos;
				// Computer strategy selection
	if (cur_game_info.board_ptr->get_num_move()
	    >= NUM_MOVE - current_level_info.num_empty_exact) {
		eval_endgame(cur_game_info.board_ptr, cur_game_info.get_player(), &pos);
		free_hash_move(cur_game_info.board_ptr->get_num_move());
	}
	else if (cur_game_info.board_ptr->get_num_move()
		 >= NUM_MOVE - current_level_info.num_empty_winlossdraw) {
		eval_winlossdraw(cur_game_info.board_ptr, cur_game_info.get_player(), &pos, komi);
		free_hash_move(cur_game_info.board_ptr->get_num_move());
	}
	else if (cur_game_info.is_random_game())
		eval_midgame(cur_game_info.board_ptr, cur_game_info.get_player(), &pos);
	else {
		int book_pos = book_move(cur_game_info.board_ptr, 
					 cur_game_info.move_history, 
					 cur_game_info.player_history);
		if (book_pos != POS_INVALID) {
			pos = book_pos;
		}
		else
			eval_midgame(cur_game_info.board_ptr, cur_game_info.get_player(), &pos);
	}
	return pos;
}

int get_computer_winlossdraw_move(double komi)
{
	int	pos;
	eval_winlossdraw(cur_game_info.board_ptr, cur_game_info.get_player(), &pos, komi);
	free_hash_move(cur_game_info.board_ptr->get_num_move());
	return pos;
}

int get_computer_exact_move()
{
	int	pos;
	eval_endgame(cur_game_info.board_ptr, cur_game_info.get_player(), &pos);
	free_hash_move(cur_game_info.board_ptr->get_num_move());
	return pos;
}


/*************************************************************************
	Home directory functions
*************************************************************************/

static std::string		home_dir;

static void user_home_dir_init()
{

		
					// Get passwd entry for current user


		char *env = getenv("HOME");
		if (env == NULL) {
					// Give up
			throw std::runtime_error(_("error obtaining the user home directory"));
		}
	
		home_dir = env;

}

const std::string &get_user_home_dir()
{
	static bool init = false;
	if (!init)
		user_home_dir_init();
	return home_dir;
}    

/*************************************************************************
	Logging function
*************************************************************************/

static const char *get_computer_player_name()
{
	if (ai_tampered)
		return "grhino-X";
	switch (computer_level) {
		case 0:
			return "grhino-1";
		case 1:
			return "grhino-2";
		case 2:
			return "grhino-3";
		case 3:
			return "grhino-4";
		case 4:
			return "grhino-5";
		case 5:
		default:
			return "grhino-C";
	}
}

#if 0
static void log_history_ios_old(const std::string &filename,
				const std::string &black_name,
				const std::string &white_name)
{
					// Random game not supported yet
	if (cur_game_info.is_random_game())
  		return;

	std::string	file = get_user_home_dir();
	file += '/';
	file += filename;
	std::ofstream of(file.c_str(), std::ios::app);
	if (!of)
		return;

	of << std::setw(10) << time(0) << ' ';
	int	black_score = cur_game_info.board_history[cur_game_info.num_history-1].board_black_score();
	int	white_score = cur_game_info.board_history[cur_game_info.num_history-1].board_white_score();

	switch (cur_game_info.get_game_result()) {
		case game_info::game_result_end:
			of << "e ";
			adjust_score(black_score, white_score);
			break;
		case game_info::game_result_timeout_black:
			of << "t ";
			black_score = 0;
			white_score = 64;
			break;
		case game_info::game_result_timeout_white:
			of << "t ";
			black_score = 64;
			white_score = 0;
			break;
		case game_info::game_result_resign_black:
			of << "r ";
			black_score = 0;
			white_score = 64;
			break;
		case game_info::game_result_resign_white:
			of << "r ";
			black_score = 64;
			white_score = 0;
			break;
	}

	of << std::setw(8) << std::left
	   << (is_computer_player(BLACK) ? get_computer_player_name() : black_name)
	   << std::right
	   << ' ' << std::setw(2) << black_score << " ( 30   0   0) ";
	of << std::setw(8) << std::left
	   << (is_computer_player(WHITE) ? get_computer_player_name() : white_name)
	   << std::right
	   << ' ' << std::setw(2) << white_score << " ( 30   0   0) ";

	for (int i = 1; i < cur_game_info.max_num_history; ++i) {
		if (cur_game_info.player_history[i-1] == BLACK)
			of << '+';
		else
			of << '-';
		of << static_cast<char>('1' + pos_to_x(cur_game_info.move_history[i-1]))
		   << static_cast<char>('1' + pos_to_y(cur_game_info.move_history[i-1]));
	}

	of << " +0\n";
}
#endif

static void log_history_GGS(const std::string &filename,
			    const std::string &black_name,
			    const std::string &white_name,
			    bool v2 = true)
{
	std::string	file = get_user_home_dir();
	file += '/';
	file += filename;
	std::ofstream of(file.c_str(), std::ios::app);
	if (!of)
		return;

	if (v2)				// GGS v2 supports synchro rand
		of << "1 ";

	of << "(;GM[Othello]PC[" << prog_name << '-' << prog_ver << "]DT[";

	char buffer[80];
	time_t t = time(0);
	if (strftime(buffer, 80, "%Y.%m.%d_%H:%M:%S.%Z", localtime(&t)))
		of << buffer;
	else
		of << t;
	of << "]PB[" << (is_computer_player(BLACK) ? get_computer_player_name() : black_name);
	of << "]PW[" << (is_computer_player(WHITE) ? get_computer_player_name() : white_name);
	of << "]TI[30:00//02:00]TY[8";
	if (cur_game_info.is_random_game())
		of << 'r' << cur_game_info.get_random_game_pieces();

	of << "]RE[";
	int	black_score = cur_game_info.board_history[cur_game_info.max_num_history-1].board_black_score();
	int	white_score = cur_game_info.board_history[cur_game_info.max_num_history-1].board_white_score();
	const char *flag;
	switch (cur_game_info.get_game_result()) {
		case game_info::game_result_end:
			adjust_score(black_score, white_score);
			flag = "";
			break;
		case game_info::game_result_timeout_black:
			flag = ":t";
			black_score = 0;
			white_score = 64;
			break;
		case game_info::game_result_timeout_white:
			flag = ":t";
			black_score = 64;
			white_score = 0;
			break;
		case game_info::game_result_resign_black:
			flag = ":r";
			black_score = 0;
			white_score = 64;
			break;
		case game_info::game_result_resign_white:
			flag = ":r";
			black_score = 64;
			white_score = 0;
			break;
		default:
			flag = ":r";
			black_score = 64;
			white_score = 0;
			break;
	}
	of << std::showpos << black_score-white_score << std::noshowpos << ".00"
	   << flag << "]BO[8 ";

	board_iterator	iter;
	iter.init_pos();
	int col = 0;
	do {
		switch (cur_game_info.board_history[0].board[iter.pos]) {
			case EMPTY:
				of << '-';
				break;
			case BLACK:
				of << '*';
				break;
			case WHITE:
				of << 'O';
				break;
		}
		col++;
		if (col == 8) {
			of << ' ';
			col = 0;
		}
	} while (iter.next());

	of << "*]";

	of << std::setprecision(2) << std::fixed << std::showpoint;
	for (int i = 1; i < cur_game_info.max_num_history; ++i) {

		if (i > 1
		    && (cur_game_info.player_history[i-1]
			== cur_game_info.player_history[i-2])) {
						// Pass
			if (cur_game_info.player_history[i-1] == BLACK)
				of << "W[pass]";
			else
				of << "B[pass]";
		}

		if (cur_game_info.player_history[i-1] == BLACK)
			of << "B[";
		else
			of << "W[";

		of << static_cast<char>('A' + pos_to_x(cur_game_info.move_history[i-1]))
		   << static_cast<char>('1' + pos_to_y(cur_game_info.move_history[i-1]));

		if (cur_game_info.time_history[i-1]) {
			of << "//";
			of << (cur_game_info.time_history[i-1]/100.0);
		}

		of << ']';
	}
	of << ";)\n";
}

void log_history(const std::string &filename,
		 const std::string &black_name,
		 const std::string &white_name)
{
					// No need to do i18n here
	if (!log_move)
		return;

	std::string	file1 = get_user_home_dir();
	file1 += "/.grhino.lock";
	std::string	file2 = file1 + ".link";
	int h;
	for (int i = 0; ; i++) {
		h = open(file1.c_str(), O_CREAT | O_EXCL, S_IRWXU);
		if (h != -1) {		// Require for NFS
					// Create hardlink in the same
					// file system
			if (CreateHardLinkA(file1.c_str(), file2.c_str(), NULL) != 0)
				break;
			struct stat s;
			if (fstat(h, &s) == 0 && s.st_nlink == 2)
				break;

					// Hardlink fails, cleanup
			close(h);
			unlink(file1.c_str());
		}

		if (i == 5) {		// Stale lock
					// Remove files
			unlink(file1.c_str());
			unlink(file2.c_str());
			i = 0;
		}

		usleep(100);		// Wait 0.1 sec
	}
	close(h);

	log_history_GGS(filename, black_name, white_name, true);

	unlink(file1.c_str());
	unlink(file2.c_str());
}

/*************************************************************************
	State update
*************************************************************************/

update_hook update_move;
update_hook update_game_list;

void update_hook::update(update_state_type u)
{
	for (size_t i = 0; i < vec.size(); ++i)
		vec[i].func(u, vec[i].data);
}

void update_hook::update_except(update_state_type u, update_func f, void *d)
{
	for (size_t i = 0; i < vec.size(); ++i) {
		if (vec[i].func != f || vec[i].data != d)
			vec[i].func(u, vec[i].data);
	}
}

void update_hook::add(update_func f, void *d)
{
	vec.push_back(update_item(f, d));
}

void update_hook::remove(update_func f, void *d)
{
	size_t i = 0;
	size_t size = vec.size();
	for ( ; i < size; ++i) {
		if (vec[i].func == f && vec[i].data == d)
			break;
	}
			// Not found
	if (i == size)
		return;

	if (i != size-1) {
		vec[i].func = vec[size-1].func;
		vec[i].data = vec[size-1].data;
	}
	vec.pop_back();
}

/*************************************************************************
	Utility functions
*************************************************************************/

const char *find_opening_name(byte_board_info *b)
{
	for (int i = 0; i < get_num_opening(); ++i) {
				// Wrong number of moves
		if (b->get_num_move() != get_opening_num_move(i))
			continue;

		int j, k;

				// Straightforward compare
		for (j = 0, k = 0; j < 64; ++j, ++k) {
			if (b->board[j] != (*get_opening_board(i))[k])
				break;
		}
		if (j == 64)
			return get_opening_name(i);

				// Transpose
		for (j = 0, k = 0; j < 64; ++j, k = (k>=56 ? (k%8)+1: k+8)) {
			if (b->board[j] != (*get_opening_board(i))[k])
				break;
		}
		if (j == 64)
			return get_opening_name(i);

				// Rotate
		for (j = 0, k = 63; j < 64; ++j, --k) {
			if (b->board[j] != (*get_opening_board(i))[k])
				break;
		}
		if (j == 64)
			return get_opening_name(i);

				// Rotate and transpose
		for (j = 0, k = 63; j < 64; ++j, k = (k<8 ? (k%8)+55: k-8)) {
			if (b->board[j] != (*get_opening_board(i))[k])
				break;
		}
		if (j == 64)
			return get_opening_name(i);
	}
	return 0;
}

const char *find_opening_name()
{
	return find_opening_name(cur_game_info.board_ptr);
}

